/*********************************************************************************
 * 
 * MIT License
 *
 * Copyright (c) 2023 shaoguangcn
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 *********************************************************************************/

/**
 * \file integer_to_chars.cpp
 */

#include <cmath>
#include <cstring>

#include "charconv.hpp"

#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable:4244)
#endif

template<typename T>
constexpr unsigned int to_chars_len_2(T value) noexcept
{
    return std::numeric_limits<T>::digits - countl_zero(value);
}

namespace detail {

template<typename T>
to_chars_result to_chars_i_generic_base(char* first, char* last, T value, int base) noexcept
{
    static_assert(std::is_integral<T>::value, "implementation bug");
    static_assert(std::is_unsigned<T>::value, "implementation bug");

    to_chars_result res;
    const unsigned int len = uint_to_chars_len(value, base);
    if (CHARCONV_UNLIKELY(last - first < len)) {
        res.ptr = last;
        res.ec = std::errc::value_too_large;
        return res;
    }

    unsigned int pos = len - 1;
    static constexpr char digits[] = {
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
        'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
        'u', 'v', 'w', 'x', 'y', 'z'
    };
    while (value >= static_cast<unsigned int>(base)) {
        const auto quotient = value / base;
        const auto remain = value % base;
        first[pos--] = digits[remain];
        value = quotient;
    }

    *first = digits[value];
    res.ptr = first + len;
    res.ec = {};
    return res;
}

template<typename T>
to_chars_result to_chars_i_16(char* first, char* last, T value) noexcept
{
    static_assert(std::is_integral<T>::value, "implementation bug");
    static_assert(std::is_unsigned<T>::value, "implementation bug");

    to_chars_result res;
    const unsigned int len = (to_chars_len_2(value) + 3) / 4;
    if (CHARCONV_UNLIKELY(last - first < len)) {
        res.ptr = last;
        res.ec = std::errc::value_too_large;
        return res;
    }

    static constexpr char digits[] = {
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
        'a', 'b', 'c', 'd', 'e', 'f'
    };
    unsigned pos = len - 1;
    while (value >= 0x100) {
        auto num = value & 0xF;
        value >>= 4;
        first[pos] = digits[num];
        num = value & 0xF;
        value >>= 4;
        first[pos - 1] = digits[num];
        pos -= 2;
    }

    if (value >= 0x10) {
        const auto num = value & 0xF;
        value >>= 4;
        first[1] = digits[num];
        first[0] = digits[value];
    } else {
        first[0] = digits[value];
    }

    res.ptr = first + len;
    res.ec = {};
    return res;
}

template<typename T>
to_chars_result to_chars_i_10(char* first, char* last, T value) noexcept
{
    static_assert(std::is_integral<T>::value, "implementation bug");
    static_assert(std::is_unsigned<T>::value, "implementation bug");

    to_chars_result res;
    const unsigned int len = uint_to_chars_len(value, 10);
    if (CHARCONV_UNLIKELY(last - first < len)) {
        res.ptr = last;
        res.ec = std::errc::value_too_large;
        return res;
    }

    static constexpr char digits[201] =
        "0001020304050607080910111213141516171819"
        "2021222324252627282930313233343536373839"
        "4041424344454647484950515253545556575859"
        "6061626364656667686970717273747576777879"
        "8081828384858687888990919293949596979899";
    unsigned int pos = len - 1;
    while (value >= 100) {
        const auto num = (value % 100) * 2;
        value /= 100;
        first[pos] = digits[num + 1];
        first[pos - 1] = digits[num];
        pos -= 2;
    }
    if (value >= 10) {
        const auto num = value * 2;
        first[1] = digits[num + 1];
        first[0] = digits[num];
    } else {
        first[0] = '0' + value;
    }

    res.ptr = first + len;
    res.ec = {};
    return res;
}

template<typename T>
to_chars_result to_chars_i_8(char* first, char* last, T value) noexcept
{
    static_assert(std::is_integral<T>::value, "implementation bug");
    static_assert(std::is_unsigned<T>::value, "implementation bug");

    to_chars_result res;
    unsigned int len;
    if CXX_IF_CONSTEXPR (std::numeric_limits<T>::digits <= 16) {
        len = value > 077777u ? 6u
	        : value > 07777u ? 5u
	        : value > 0777u ? 4u
	        : value > 077u ? 3u
	        : value > 07u ? 2u
	        : 1u;
    } else {
        len = (to_chars_len_2(value) + 2) / 3;
    }

    if (CHARCONV_UNLIKELY(last - first < len)) {
        res.ptr = last;
        res.ec = std::errc::value_too_large;
        return res;
    }

    unsigned int pos = len - 1;
    while (value >= 0100) {
        auto num = value & 7;
        value >>= 3;
        first[pos] = '0' + num;
        num = value & 7;
        value >>= 3;
        first[pos - 1] = '0' + num;
        pos -= 2;
    }

    if (value >= 010) {
        const auto num = value & 7;
        value >>= 3;
        first[1] = '0' + num;
        first[0] = '0' + value;
    } else {
        first[0] = '0' + value;
    }

    res.ptr = first + len;
    res.ec = {};
    return res;
}

template<typename T>
to_chars_result to_chars_i_2(char* first, char* last, T value) noexcept
{
    static_assert(std::is_integral<T>::value, "implementation bug");
    static_assert(std::is_unsigned<T>::value, "implementation bug");

    to_chars_result res;
    const unsigned int len = to_chars_len_2(value);

    if (CHARCONV_UNLIKELY(last - first < len)) {
        res.ptr = last;
        res.ec = std::errc::value_too_large;
        return res;
    }

    unsigned int pos = len - 1;
    while (pos) {
        first[pos--] = '0' + (value & 1);
        value >>= 1;
    }
    // First digit is always '1' because to_chars_len_2() skips
    // leading zero bits and to_chars() handles zero values directly.
    first[0] = '1';

    res.ptr = first + len;
    res.ec = {};
    return res;
}

template<typename T>
to_chars_result to_chars_i(char* first, char* last, T value, int base = 10)
{
    CHARCONV_ASSERT(2 <= base && base <= 36);
    using unsigned_type = typename std::make_unsigned<T>::type;
    unsigned_type unisgned_value = static_cast<unsigned_type>(value);
    if (CHARCONV_UNLIKELY(first == last)) {
        return { last, std::errc::value_too_large };
    }

    if (value == 0) {
        *first = '0';
        return { first + 1, std::errc{} };
    } else if CXX_IF_CONSTEXPR (std::is_signed<T>::value) {
        if (value < 0) {
            *first++ = '-';
            // unisgned_value = static_cast<unsigned_type>(~value) + static_cast<unsigned_type>(1);
            unisgned_value = static_cast<unsigned_type>(0 - value);
        }
    }

    switch (base)
    {
    case 16:
        return to_chars_i_16(first, last, unisgned_value);
    case 10:
        return to_chars_i_10(first, last, unisgned_value);
    case 8:
        return to_chars_i_8(first, last, unisgned_value);
    case 2:
        return to_chars_i_2(first, last, unisgned_value);
    default:
        return to_chars_i_generic_base(first, last, unisgned_value, base);
    }
}

} // namespace detail

to_chars_result to_chars(char* first, char* last, char value, int base)
{
    return detail::to_chars_i(first, last, value, base);
}

to_chars_result to_chars(char* first, char* last, signed char value, int base)
{
    return detail::to_chars_i(first, last, value, base);
}

to_chars_result to_chars(char* first, char* last, unsigned char value, int base)
{
    return detail::to_chars_i(first, last, value, base);
}

to_chars_result to_chars(char* first, char* last, short value, int base)
{
    return detail::to_chars_i(first, last, value, base);
}

to_chars_result to_chars(char* first, char* last, unsigned short value, int base)
{
    return detail::to_chars_i(first, last, value, base);
}

to_chars_result to_chars(char* first, char* last, int value, int base)
{
    return detail::to_chars_i(first, last, value, base);
}

to_chars_result to_chars(char* first, char* last, unsigned int value, int base)
{
    return detail::to_chars_i(first, last, value, base);
}

to_chars_result to_chars(char* first, char* last, long value, int base)
{
    return detail::to_chars_i(first, last, value, base);
}

to_chars_result to_chars(char* first, char* last, unsigned long value, int base)
{
    return detail::to_chars_i(first, last, value, base);
}

to_chars_result to_chars(char* first, char* last, long long value, int base)
{
    return detail::to_chars_i(first, last, value, base);
}

to_chars_result to_chars(char* first, char* last, unsigned long long value, int base)
{
    return detail::to_chars_i(first, last, value, base);
}

#ifdef _MSC_VER
# pragma warning(pop)
#endif